<?php
/**
 *  Todo.php   - Simple TODO Manager -
 *  @version   0.2.8
 *  @see       http://0-oo.net/sbox/php-tool-box/todo
 *  @copyright 2008-2009 dgbadmin@gmail.com
 *  @license   http://0-oo.net/pryn/MIT_license.txt (The MIT license)
 */
class Todo {
    /** 文字コード */
    const ENCODING = 'UTF-8';
 
    /** サーバに保存するファイル名の文字コード */
    //const FILE_NAME_ENCODING = 'UTF-8';     //Linuxその1
    const FILE_NAME_ENCODING = 'EUC-JP';      //Linuxその2
    //const FILE_NAME_ENCODING = 'Shift_JIS'; //Windows
 
    /** TODOデータを保存するディレクトリ */
    const DATA_DIR = '2data';
 
    /** データファイルの拡張子 */
    const DATA_EXT = 'txt';
 
    /** カテゴリ名として許可する正規表現 */
    const CAT_REGEX = '^[^\\\\./:*?"<>|]{1,20}$';
 
    /** ページタイトル */
    const TITLE = 'KToDo';
 
    /** バックアップの保存期限 */
    const BACKUP_TIME = '-7 day';
 
    /** 優先度の最大値 */
    const PRI_MAX = 5;
 
    public $cat;
    public $cats;
    public $list;
 
    /**
     *    コンストラクタ
     *    @param    string    $cat    カテゴリ
     */
    public function __construct($cat) {
        $this->cat = $cat;
        mb_internal_encoding(Todo::ENCODING);
        mb_regex_encoding(Todo::ENCODING);
    }
    /**
     *    表示の準備
     */
    public function setUp() {
        if ($this->isValidCat()) {
            if ($_REQUEST['delete'] && $this->_deleteCat()) {
            } else {
                if ($_POST['update']) {
                    $this->_updateList();
                }
                $this->list = $this->_getList();
            }
        }
        $this->cats = $this->_getCategories();
    }
    /**
     *    カテゴリチェック
     *    @return    boolean    許可されるカテゴリかどうか
     */
    public function isValidCat() {
        return mb_eregi(Todo::CAT_REGEX, $this->cat);
    }
    /**
     *    TODOリストのファイルパスを取得する
     *    @return    string    パス
     */
    public function getPath() {
        $cat = mb_convert_encoding($this->cat, Todo::FILE_NAME_ENCODING);
        return Todo::DATA_DIR . '/' . $cat . '.' . Todo::DATA_EXT;
    }
    /**
     *    カテゴリを全て取得する
     *    @return    array    全てのカテゴリのカテゴリ名とファイルサイズ
     */
    private function _getCategories() {
        $h = opendir(Todo::DATA_DIR);
        if (!$h) {
            exit('data directory is not found.');
        }
        $cats = array();
        $limit = date('YmdHis', strToTime(Todo::BACKUP_TIME));
        while (false !== ($file = readdir($h))) {
            if (is_dir($file)) {
                continue;
            }
            $arr = explode('.', $file);
            $path = Todo::DATA_DIR . '/' . $file;
            if (count($arr) == 3) {        //バックアップの場合
                if ($arr[2] < $limit) {    //期限切れは削除
                    unlink($path);
                }
            } else {    //最新版の場合
                $cat = mb_convert_encoding($arr[0], Todo::ENCODING, Todo::FILE_NAME_ENCODING);
                $cats[$cat] = filesize($path);
            }
        }
        closedir($h);
        ksort($cats);
        return $cats;
    }
    /**
     *    TODOリストを更新して保存する
     */
    private function _updateList() {
        $oldCat = new Todo($_POST['oldcat']);   //変更前のカテゴリ
        $newPath = $this->getPath();
        if (is_file(strtoupper(__FILE__))) {    //ファイルパスで大文字小文字を区別しない場合
            $change = strcasecmp($oldCat->cat, $this->cat);
        } else {
            $change = ($oldCat->cat != $this->cat);
        }
        if ($change && is_file($newPath)) {
            return;    //変更後のカテゴリが既に存在する場合は更新しない
        }
 
        $oldPath = $oldCat->getPath();
        if ($oldCat->isValidCat() && is_file($oldPath)) {
            rename($oldPath, $oldPath . '.' . date('YmdHis'));    //バックアップ
        }
 
        foreach ($_POST['todo'] as $post) {
            if ($post[1] != '') {    //TODO未入力は削除
                $data .= implode("\t", $post) . "\n";
            }
        }
        file_put_contents($newPath, $data);
    }
    /**
     *    TODOリストを取得する
     *    @return    array    TODOリスト
     */
    private function _getList() {
        $path = $this->getPath();
        if (!is_file($path)) {    //新規の場合
            return array('');
        }
        $list = explode("\n", file_get_contents($path));
        rsort($list);    //優先度順でソート
        return $list;
    }
    /**
     *    カテゴリを削除する
     *    @return    boolean    削除できたかどうか
     */
    private function _deleteCat() {
        $path = $this->getPath();
        if (!is_file($path) || filesize($path)) {    //todoが残っている場合は削除させない
            return false;
        }
        $this->cat = null;
        return unlink($path);
    }
}
 
//----- Global Functions -----
/**
 *    HTMLエスケープ
 *    @param    string    $val    エスケープしたい文字列
 *    @return    string    エスケープした文字列
 */
function h($val) {
    return htmlspecialchars($val, ENT_QUOTES);
}
/**
 *    option要素を出力する
 *    @param    string    $val    optionの値
 *    @param    string    $selected    selectedにすべき値
 */
function echoOption($val, $selectedVal) {
    if ($val == $selectedVal) {
        $selected = 'selected="selected"';
    }
    echo "<option $selected>$val</option>\n";
}
//----------------------------
 
 
$todo = new Todo($_REQUEST['cat']);
$todo->setUp();
 
 
ini_set('default_charset', Todo::ENCODING);
?>
<<?php ?>?xml version="1.0" encoding="<?php echo Todo::ENCODING; ?>" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=<?php echo Todo::ENCODING; ?>" />
<meta http-equiv="Content-Style-Type"  content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title><?php echo h(Todo::TITLE . ' - ' . $todo->cat); ?></title>
<link rel="stylesheet" type="text/css" href="/js/Pryn.css" />
<style type="text/css">
div#hd{        padding-top: 1em; }
h1{            margin: 1em 0 0; }
h1 a{          text-decoration: none; }
div#bd, li#add{ font-size: 100%; }
div.yui-g{     width: 43em; }
div#cat{       margin-bottom: 1em; font-size: 161.6%; }
tr.row:hover{  background-color: #79a; }
th, td{        border: solid #79a 1px; }
td{            padding: 0; }
select{        margin: 1px; width: 4.1em; height: 1.5em; text-align: center; }
table#todo input, select{ border-width: 0; }
input.todo{    margin: 2px; margin-left: 5px; width: 21.5em; }
input.date{    margin: 1px; width: 5.6em; text-align: center; }
input#update{  float: right; margin-right: 1em; }
li{            padding-top: 0.5em; font-size: 131%; }
input#new-cat{ width: 5em; }
div#ft{        text-align: center; }
</style>
 
</head>
 
<body>
<div id="doc" class="yui-t2">
 
<!-- ヘッダー -->
<div id="hd"><h1><a href="?"><?php echo h(Todo::TITLE); ?></a></h1></div>
 
<div id="bd">
 
<div id="yui-main"><div class="yui-b"><div class="yui-g">
<?php
$i = -1;
if ($todo->isValidCat()) {
    ?>
    <form method="post" action="?">
 
    <div id="cat">
    カテゴリ ： <input type="text" name="cat" value="<?php echo h($todo->cat); ?>" />
    <input type="hidden" name="oldcat" value="<?php echo h($todo->cat); ?>" />
    </div>
 
    <table id="todo" summary="todo">
    <tr><th>優先度</th><th>TODO</th><th>開始日 </th><th>期限</th><th>状態</th></tr>
 
    <?php
    $styleClasses = array('', 'todo', 'date han', 'date han');
    $statuses = array('<!-- -->', '保留', '完了');
 
    foreach ($todo->list as $i => $row) {
        $task = explode("\t", $row);
        if ($task[4] == $statuses[2]) {    //完了は出力しない
            continue;
        }
        ?>
        <tr class="row">
 
        <!-- 優先度 -->
        <td>
        <select name="todo[<?php echo $i; ?>][]">
        <?php
        for ($j = 1; $j < Todo::PRI_MAX + 1; $j++) {
            echoOption($j, $task[0]);
        }
        ?>
        </select>
        </td>
 
        <?php
        //TODO、開始日、期限
        for ($j = 1; $j < 4; $j++) {
            ?>
            <td>
            <input type="text" name="todo[<?php echo $i; ?>][]"
             value="<?php echo h($task[$j]); ?>" class="<?php echo $styleClasses[$j]; ?>" />
            </td>
            <?php
        }
        ?>
 
        <!-- 状態 -->
        <td>
        <select name="todo[<?php echo $i; ?>][]">
        <?php
        foreach ($statuses as $status) {
            echoOption($status, $task[4]);
        }
        ?>
        </select>
        </td>
 
        </tr>
        <?php
    }
    ?>
    </table>
 
    <div>
    <input type="submit" id="update" name="update" value="更新" />
    </div>
 
    </form>
 
    <div class="clear"></div>
    <?php
} else if ($todo->cat) {
    ?>
    <span class="error">残念ですが、このカテゴリ名（ <?php echo h($todo->cat); ?> ）は使えません</span>
    <?php
}
?>
</div></div></div>
 
<!-- カテゴリリスト -->
<div id="cats" class="yui-b">
<ul>
<?php
foreach ($todo->cats as $cat => $fileSize) {
    $href = '?cat=' . urlencode($cat);
    ?>
    <li>
    <a href="<?php echo $href; ?>"><?php echo h($cat); ?></a>
    <?php
    if (!$fileSize) {    //todoが無いカテゴリは削除できる
        ?>
        <a href="<?php echo $href; ?>&amp;delete=do" title="このカテゴリを削除する">［削除］</a>
        <?php
    }
    ?>
    </li>
    <?php
}
?>
 
<li id="add">
<form method="post" action="?">
<div>
<input type="text" id="new-cat" name="cat" />
<input type="submit" value="追加" />
</div>
</form>
</li>
 
</ul>
</div>
 
</div>
 
<!-- フッター -->
<div id="ft">
</div>
 
</div>
 
<script type="text/javascript" src="/js/Pryn.js"></script>
<script type="text/javascript" src="/js/YahhoCal.js"></script>
<script type="text/javascript" src="/js/GCalHolidays.js"></script>
<script type="text/javascript">
YahhoCal.loadYUI();
YahhoCal.setMondayAs1st();
Pryn.addEvent(window, "load", function() {
    (function(input) {
        var acs = new Pryn.ClassAccessor(input);
        if (acs.hasClass("date")) {
            Pryn.addEvent(input, "click", function() {
                YahhoCal.render(YAHOO.util.Dom.generateId(input));
            });
            acs.addClass("clickable");
        }
    }).foreach($T("input"));
});
</script>
 
</body>
